Skip to main content

media_pp\elements\driver\webrtc/
track.rs

1use std::{
2    sync::{
3        Arc, Mutex,
4        atomic::{AtomicU64, Ordering},
5    },
6    time::Duration,
7};
8
9use crate::pp_log::{PpLog, pp_error, pp_info};
10use crossbeam_channel::{Receiver, Sender, TrySendError, select};
11use str0m::{
12    change::{SdpAnswer, SdpOffer},
13    format::Codec,
14    media::{Direction, MediaKind, Mid},
15};
16
17use crate::{
18    buffer::MediaBuffer,
19    bus::{Bus, BusEvent},
20    control::{
21        ControlMsg, ControlReceiver, RequestKind, apply_finish, apply_one, drain_control,
22        wait_out_pause,
23    },
24    element::{Element, ElementType, Sink, Source, SourceElement, element_pp_log},
25    error::Result,
26    pad::SrcPad,
27};
28
29use super::command::{Command, TrackId, WebRtcError};
30
31/// Cheaply-cloneable handle for requesting new tracks, completing
32/// renegotiation, and picking up newly-attached tracks — same spirit as
33/// [`crate::elements::AppSourceHandle`]. Cloning shares one queue of
34/// pending [`WebRtcHandle::next_track`] results, same as any other
35/// multi-consumer channel — only one clone's call actually receives a
36/// given track, so in practice only one place in the app should be
37/// draining it.
38#[derive(Clone)]
39pub struct WebRtcHandle {
40    pub(super) next_id: Arc<AtomicU64>,
41    pub(super) command_tx: Sender<Command>,
42    pub(super) new_track_rx:
43        Receiver<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)>,
44}
45
46impl WebRtcHandle {
47    /// Requests a new track of `kind`/`direction`. Blocks only while the
48    /// peer's bounded command queue is full; once the command is accepted,
49    /// returns the locally assigned [`TrackId`]. This does not mean SDP
50    /// negotiation has completed — receive the attached track through
51    /// [`WebRtcHandle::next_track`]. Returns [`WebRtcError::Closed`] without
52    /// yielding a `TrackId` if the peer loop has already stopped.
53    ///
54    /// `codec` is what [`WebRtcTrackSink::consume`] on the resulting track
55    /// will actually be fed (an encoder's output, or a packet relayed
56    /// verbatim from another track) — used to pick the matching payload
57    /// type out of whatever this connection negotiates for the track,
58    /// instead of guessing. If this connection never negotiates `codec` for
59    /// it, pushed buffers are silently dropped, same as an unopened track.
60    pub fn add_track(
61        &self,
62        kind: MediaKind,
63        direction: Direction,
64        codec: Codec,
65    ) -> Result<TrackId> {
66        let id = TrackId(self.next_id.fetch_add(1, Ordering::Relaxed));
67        self.command_tx
68            .send(Command::AddTrack(id, kind, direction, codec))
69            .map_err(|_| WebRtcError::Closed)?;
70        Ok(id)
71    }
72
73    /// Blocks until the next track attaches — either one requested via
74    /// [`WebRtcHandle::add_track`] (on either side) once its `Mid` exists,
75    /// or one the remote peer added on its own. Returns the `TrackId` (so
76    /// the caller can match it against what `add_track` returned — a `Mid`
77    /// alone doesn't exist yet at `add_track` time, see [`TrackId`]'s own
78    /// docs) alongside the `Mid`/`MediaKind` str0m assigned it and the
79    /// `WebRtcTrackSink`/`WebRtcTrackSource` pair to send/receive on it.
80    /// `Err` once `WebRtcPeer` (and its `run`) is gone and every
81    /// already-attached track has been drained.
82    pub fn next_track(
83        &self,
84    ) -> Result<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)> {
85        self.new_track_rx
86            .recv()
87            .map_err(|_| WebRtcError::Closed.into())
88    }
89
90    /// Feeds a remote answer back in, completing a renegotiation started by
91    /// [`WebRtcHandle::add_track`]. A no-op if `WebRtcPeer` (and its `run`)
92    /// is already gone.
93    pub fn set_answer(&self, answer: SdpAnswer) {
94        let _ = self.command_tx.send(Command::SetAnswer(answer));
95    }
96
97    /// Accepts a fresh offer from the *remote* peer (their own
98    /// renegotiation) and returns the resulting answer for the caller to
99    /// ship back over its own signaling transport. Blocks until
100    /// `WebRtcPeer::run` has actually applied it.
101    pub fn accept_remote_offer(&self, offer: SdpOffer) -> Result<SdpAnswer> {
102        let (reply_tx, reply_rx) = crossbeam_channel::bounded(0);
103        self.command_tx
104            .send(Command::AcceptOffer(offer, reply_tx))
105            .map_err(|_| WebRtcError::Closed)?;
106        reply_rx
107            .recv()
108            .map_err(|_| WebRtcError::Closed)?
109            .map_err(Into::into)
110    }
111}
112
113/// One outbound track. A plain [`Sink`] — no bespoke push API, it links
114/// into a [`crate::pipeline::ChainBuilder`] exactly like
115/// [`crate::elements::RtspSink`] or any other terminal sink.
116/// `consume()` only ever hands off to `WebRtcPeer::run`'s own thread via a
117/// channel send; the actual str0m write happens over there.
118pub struct WebRtcTrackSink {
119    pp_log: PpLog,
120    id: TrackId,
121    command_tx: Sender<Command>,
122}
123
124impl WebRtcTrackSink {
125    pub(super) fn new(id: TrackId, command_tx: Sender<Command>) -> Self {
126        Self {
127            id,
128            command_tx,
129            pp_log: element_pp_log(
130                ElementType::WebRtcPeer,
131                &format!("webrtc-track-{}", id.0),
132                None,
133            ),
134        }
135    }
136}
137
138impl Element for WebRtcTrackSink {
139    fn name(&self) -> Arc<str> {
140        format!("webrtc-track-{}", self.id.0).into()
141    }
142
143    fn element_type(&self) -> ElementType {
144        ElementType::WebRtcPeer
145    }
146
147    fn pp_log(&self) -> &PpLog {
148        &self.pp_log
149    }
150
151    fn pp_log_mut(&mut self) -> &mut PpLog {
152        &mut self.pp_log
153    }
154}
155
156impl Sink for WebRtcTrackSink {
157    fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
158        if !matches!(buf, MediaBuffer::Packet(_) | MediaBuffer::Eos) {
159            let kind = match buf {
160                MediaBuffer::Video(_) => "Video",
161                MediaBuffer::Audio(_) => "Audio",
162                MediaBuffer::Packet(_) | MediaBuffer::Eos => unreachable!("matched above"),
163            };
164            pp_error!(self, "unsupported buffer: {kind}");
165            return Err(WebRtcError::UnsupportedBuffer(kind).into());
166        }
167        // `WebRtcPeer::run` gone (channel disconnected) means this track is
168        // dead — surface it as `Err` rather than swallowing it, so whatever
169        // pipeline this `Sink` is plugged into (its own `Queue`, its own
170        // `Bus`) actually learns about it instead of silently sending into
171        // a void forever. Non-fatal by the same convention as any other
172        // `Sink::consume` failure (see `Queue`'s own docs) — just no longer
173        // an invisible one.
174        //
175        // A full channel (`WebRtcPeer::run` backed up) drops the newest
176        // buffer instead — same as an unopened track (see `add_track`'s
177        // docs) — but isn't reported on a `Bus`: unlike `WebRtcPeer::run`,
178        // which only ever borrows a `Bus` for the duration of one `run()`
179        // call, `WebRtcTrackSink` is a handle the caller can keep past
180        // `Driver::stop()`, so storing one here would keep that `Bus`'s
181        // channel open indefinitely — including past whatever's waiting on
182        // `BusReceiver::iter()` to finish once every sender is gone.
183        match self.command_tx.try_send(Command::Push(self.id, buf)) {
184            Ok(()) | Err(TrySendError::Full(_)) => Ok(()),
185            Err(TrySendError::Disconnected(_)) => {
186                pp_error!(self, "WebRtcPeer::run gone — track is dead");
187                Err(WebRtcError::Closed.into())
188            }
189        }
190    }
191
192    fn control(&mut self, _msg: ControlMsg) -> Result<()> {
193        // Terminal, same as AppSink/RtspSink: nothing buffered or
194        // downstream to flush/forward for any ControlMsg.
195        Ok(())
196    }
197}
198
199/// One inbound track — the mirror image of [`WebRtcTrackSink`]. A plain
200/// [`SourceElement`], same shape as [`crate::elements::AppSource`]: it
201/// links into its own [`crate::pipeline::Pipeline`] via `src_pads()` like
202/// any other source. The difference from `AppSource` is only *who* feeds
203/// it — instead of an [`crate::elements::AppSourceHandle`] the app calls
204/// itself, [`crate::driver::Driver::run`] pushes into the sending half of this same
205/// channel internally, from its own thread, for every `Event::MediaData`
206/// on this track's `Mid`. Nothing here ever calls back into caller-supplied
207/// code from `WebRtcPeer::run`'s own thread — that thread only ever touches
208/// this crate's own types (see the module docs for why `WebRtcPeer` hands
209/// tracks out through [`WebRtcHandle::next_track`] instead of a callback).
210pub struct WebRtcTrackSource {
211    pp_log: PpLog,
212    name: Arc<str>,
213    pad: SrcPad,
214    data_rx: Receiver<MediaBuffer>,
215    codec: Arc<Mutex<Option<Codec>>>,
216}
217
218impl WebRtcTrackSource {
219    pub(super) fn new(
220        name: impl Into<String>,
221        data_rx: Receiver<MediaBuffer>,
222        codec: Arc<Mutex<Option<Codec>>>,
223    ) -> Self {
224        let name: Arc<str> = name.into().into();
225        let pp_log = element_pp_log(ElementType::WebRtcPeer, &name, None);
226        let pad = SrcPad::new(format!("{name}_src"));
227        Self {
228            name,
229            pp_log,
230            pad,
231            data_rx,
232            codec,
233        }
234    }
235
236    /// The codec this track is actually carrying, as seen on the most
237    /// recently received packet's RTP payload type — `None` until the
238    /// first one arrives. Unlike [`WebRtcHandle::add_track`]'s `codec`
239    /// (which the *caller* declares up front for an outbound track), an
240    /// inbound track's codec isn't knowable ahead of time: SDP negotiation
241    /// can accept several codecs for one `m=` line, and only the packets
242    /// actually arriving say which one the remote side picked (see
243    /// `Event::MediaData`'s own `params` field). Whatever's downstream
244    /// (e.g. a decoder) needs a keyframe before it can do anything useful
245    /// anyway, so waiting for the first packet to learn the codec isn't an
246    /// extra constraint in practice.
247    pub fn codec(&self) -> Option<Codec> {
248        *self.codec.lock().unwrap()
249    }
250}
251
252impl Element for WebRtcTrackSource {
253    fn name(&self) -> Arc<str> {
254        self.name.clone()
255    }
256
257    fn element_type(&self) -> ElementType {
258        ElementType::WebRtcPeer
259    }
260
261    fn pp_log(&self) -> &PpLog {
262        &self.pp_log
263    }
264
265    fn pp_log_mut(&mut self) -> &mut PpLog {
266        &mut self.pp_log
267    }
268}
269
270impl Source for WebRtcTrackSource {
271    fn src_pads(&mut self) -> &mut [SrcPad] {
272        std::slice::from_mut(&mut self.pad)
273    }
274}
275
276impl SourceElement for WebRtcTrackSource {
277    /// Identical shape to [`crate::elements::AppSource::run`]: selects on
278    /// `control` and its own data channel together, so `Stop`/`Pause`
279    /// never wait behind a remote peer that's gone quiet. The data channel
280    /// disconnecting — `WebRtcPeer` gone, whether from `Stop` or the
281    /// connection dying on its own — ends this the same way `AppSource`
282    /// ends when every `AppSourceHandle` is dropped: one final `Eos`, no
283    /// error.
284    fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
285        pp_info!(self, "started");
286        loop {
287            if drain_control(control, self, bus)?.stopped {
288                pp_info!(self, "stopped");
289                return Ok(());
290            }
291
292            select! {
293                recv(control.rx) -> req => {
294                    match req {
295                        Ok(req) => {
296                            match req.kind {
297                                RequestKind::Finish => {
298                                    apply_finish(self, bus, &req.ack);
299                                    pp_info!(self, "finished");
300                                    return Ok(());
301                                }
302                                RequestKind::Control(msg) => {
303                                    if apply_one(self, bus, msg, &req.ack)? {
304                                        pp_info!(self, "stopped");
305                                        return Ok(());
306                                    }
307                                    if msg == ControlMsg::Pause
308                                        && wait_out_pause(control, self, bus)?
309                                    {
310                                        pp_info!(self, "stopped");
311                                        return Ok(());
312                                    }
313                                }
314                            }
315                        }
316                        // The Pipeline itself is gone — nothing left to drive this.
317                        Err(_) => {
318                            pp_info!(self, "run: control channel gone, ending");
319                            return Ok(());
320                        }
321                    }
322                }
323                recv(self.data_rx) -> buf => {
324                    match buf {
325                        Ok(buf) if buf.is_eos() => {
326                            pp_info!(self, "event=eos phase=source_received");
327                            break;
328                        }
329                        Ok(buf) => {
330                            if let Err(error) = self.pad.push(buf) {
331                                bus.post(
332                                    &self.pp_log,
333                                    BusEvent::Error {
334                                        element_type: ElementType::WebRtcPeer,
335                                        name: self.name.clone(),
336                                        error,
337                                    },
338                                );
339                            }
340                        }
341                        // `WebRtcPeer` gone — this track (or the whole peer) is done.
342                        Err(_) => {
343                            pp_info!(self, "run: WebRtcPeer gone, ending");
344                            break;
345                        }
346                    }
347                }
348            }
349        }
350        // The data channel ending (above) can race a `Stop` sent at the
351        // same moment — e.g. stopping the *upstream* `WebRtcPeer` (via its
352        // `DriverRunner`) disconnects this exact channel, and a caller
353        // stopping this `Pipeline` too, right after, can land its `Stop` in
354        // `control`'s queue after `select!` already picked the data arm.
355        // Ack it (a no-op otherwise) so `ControlSender::send`'s rendezvous
356        // never blocks forever waiting for an ack this thread would
357        // otherwise never get around to sending.
358        while let Some((_msg, ack)) = control.try_recv() {
359            let _ = ack.send(());
360        }
361        self.pad.push_eos(&self.pp_log)
362    }
363
364    /// No timeline of its own — same reasoning as
365    /// [`crate::elements::AppSource::seek`]: a WebRTC connection has
366    /// nothing to reposition.
367    fn seek(&mut self, target: Duration) -> Result<Duration> {
368        Ok(target)
369    }
370}